<template>
  <Card>
    <Space direction="vertical" style="width: 100%">
      <Space>
        <ACheckbox v-model:checked="isExpand">展开/折叠</ACheckbox>
        <ACheckbox v-model:checked="checkAll">全选/全不选</ACheckbox>
        <ACheckbox v-model:checked="linkage">父子联动</ACheckbox>
      </Space>
      <Card>
        <Tree
          :height="200"
          :checkedKeys="value"
          :tree-data="menus"
          v-model:expanded-keys="treeExpandedKeys"
          checkable
          checkStrictly
          placeholder="请选择权限"
          @check="check"
        />
      </Card>
    </Space>
  </Card>
</template>

<script lang="tsx" setup>
import { PropType, computed, ref, watch } from 'vue';
import {
  Checkbox as ACheckbox,
  Card,
  Form,
  Space,
  Tree,
  type TreeProps,
} from 'ant-design-vue';
import { formatTree, treeToArray } from '@/utils/utils';
import { fetchMenus } from '@/api/system';

defineProps({
  value: {
    type: Array as PropType<number[]>,
    default: () => [],
  },
});
const emit = defineEmits(['update:value']);
const menus = ref<TreeProps['treeData']>([]);

const formItemContext = Form.useInjectFormItemContext();
const triggerChange = (value: number[]) => {
  emit('update:value', value);
  formItemContext.onFieldChange();
};

fetchMenus().then((res) => {
  menus.value = formatTree(res.data, {
    valueKey: 'key',
    value: 'id',
    labelKey: 'title',
  }) as unknown as TreeProps['treeData'];
});

const allMenuIds = computed(() => {
  return treeToArray(menus.value!, {
    label: 'title',
    value: 'key',
  }).map((item) => item.value);
});

const isExpand = ref<boolean>(false);
const treeExpandedKeys = ref<number[]>([]);

watch(
  () => isExpand.value,
  (value) => {
    treeExpandedKeys.value = value ? [...allMenuIds.value] : [];
  },
);

const checkAll = ref<boolean>(false);
watch(
  () => checkAll.value,
  (value) => {
    triggerChange(value ? [...allMenuIds.value] : []);
  },
);

const linkage = ref<boolean>(true);

const check: TreeProps['onCheck'] = (
  checkedKeys: any,
  { checked, node }: any,
) => {
  if (linkage.value) {
    triggerChange(checkedKeys.checked);
    if (checked) {
      checkNodeParentIds(node, checkedKeys.checked);
      checkNodeChildrenIds(node, checkedKeys.checked);
      triggerChange(checkedKeys.checked);
    } else {
      unloadNodeParentIds(node, checkedKeys.checked);
      unloadNodeChildrenIds(node, checkedKeys.checked);
      triggerChange(checkedKeys.checked);
    }
  } else {
    triggerChange(checkedKeys.checked);
  }
};

const checkNodeParentIds = (node: any, ids: any[]) => {
  if (node.parent) {
    !ids.includes(node.parent.key) && ids.push(node.parent.key);
    checkNodeParentIds(node.parent, ids);
  }
  return ids;
};
const checkNodeChildrenIds = (node: any, ids: any[]) => {
  if (node.children.length) {
    node.children.forEach((item: any) => {
      !ids.includes(item.key) && ids.push(item.key);
      checkNodeChildrenIds(item, ids);
    });
  }
  return ids;
};

const unloadNodeParentIds = (node: any, ids: any[]) => {
  if (node.parent && isSingle(node, ids)) {
    const index = ids.indexOf(node.parent.key);
    if (index !== -1) {
      ids.splice(index, 1);
    }
    unloadNodeParentIds(node.parent, ids);
  }
  return ids;
};
const unloadNodeChildrenIds = (node: any, ids: any[]) => {
  if (node.children.length) {
    node.children.forEach((item: any) => {
      const index = ids.indexOf(item.key);
      if (index !== -1) {
        ids.splice(index, 1);
      }
      unloadNodeChildrenIds(item, ids);
    });
  }
  return ids;
};
const isSingle = (node: any, ids: any[]) => {
  const intersection = node.parent.children.filter((item: any) =>
    ids.includes(item.key),
  );
  return intersection.length === 0;
};
</script>
